#ifndef AVALON_ASYNC_HPP
#define AVALON_ASYNC_HPP
#include <boost/aligned_storage.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/exception_ptr.hpp>
#include <boost/noncopyable.hpp>
#pragma warning(disable : 4267)
#include <boost/thread.hpp>
#undef ERROR
#pragma warning(default : 4267)
#include "Avalon/Base/Out.hpp"
#include "Avalon/Threading/TimeoutException.hpp"

namespace Avalon {
namespace Threading {

  /*! \class Async
   *  \brief Stores the result of an asynchronous operation.
   */
  template<typename T>
  class Async : private boost::noncopyable {
    public:

      //! Called when the Async is no longer PENDING.
      typedef boost::function<void ()> FinishedSlot;

      /*! \enum State
       *  \brief Enumerates the states of an operation.
       */
      enum State {

        //! The operation is currently pending.
        PENDING = 1,

        //! The operation succeeded.
        COMPLETE = 2,

        //! The operation resulted in an exception.
        EXCEPTION = 4,
      };

      //! Constructs an Async.
      Async()
          : m_result(NULL),
            m_state(PENDING) {}

      //! Constructs an Async whose result has already been computed.
      /*!
        \param result The result of the operation.
      */
      Async(const T& result)
          : m_result(new(&m_storage) T(result)),
            m_state(COMPLETE) {}

      ~Async() {
        if(m_result != NULL) {
          m_result->~T();
          operator delete(m_result, &m_storage);
        }
      }

      //! Waits for the operation to finish.
      /*!
        \return The State of the operation.
      */
      template<typename Lock>
      State Wait(Lock& lock) const {
        boost::unique_lock<boost::mutex> waitLock(this->m_mutex);
        if(this->m_state != PENDING) {
          return this->m_state;
        }
        lock.unlock();
        m_isFinishedCondition.wait(waitLock);
        lock.lock();
        assert(this->m_state != PENDING);
        return this->m_state;
      }

      //! Sets the result of this Async.
      /*!
        \param result The result of this Async.
      */
      void SetResult(const T& result) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == PENDING);
        m_result = new(&m_storage) T(result);
        SetState(COMPLETE, lock);
      }

      //! Returns the exception.
      const boost::exception_ptr& GetException() const {
        assert(m_state == EXCEPTION);
        return m_exception;
      }

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      template<typename E>
      void SetException(const E& e) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == PENDING);
        m_exception = boost::copy_exception(e);
        SetState(EXCEPTION, lock);
      }

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      void SetException(const boost::exception_ptr& e) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == PENDING);
        m_exception = e;
        SetState(EXCEPTION, lock);
      }

      //! Copies the exception held by another async.
      /*!
        \param async The async to copy the exception from.
      */
      template<typename U>
      void CopyException(const Async<U>& async) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == PENDING);
        m_exception = async.GetException();
        SetState(EXCEPTION, lock);
      }

      //! Resets the result, permitting its reuse.
      void ResetResult() {
        if(m_state == PENDING) {
          return;
        }
        m_exception.reset();
        m_state = PENDING;
        m_result->~T();
        operator delete(m_result, &m_storage);
        m_result = NULL;
      }

      //! Resets this Async including all slots, permitting its reuse.
      void Reset() {
        if(m_state == PENDING) {
          return;
        }
        m_state = PENDING;
        m_exception.reset();
        m_result->~T();
        operator delete(m_result, &m_storage);
        m_result = NULL;
        m_finishedSlot.reset();
      }

      //! Returns the result of the operation.
      const T& Get() const {
        assert(this->m_state & (COMPLETE | EXCEPTION));
        if(this->m_state == EXCEPTION) {
          boost::rethrow_exception(m_exception);
        }
        return *m_result;
      }

      //! Returns the result of the operation.
      /*!
        \param timeout The amount of time to wait before timing out.
        \return The result of the operation.
      */
      const T& Get(boost::posix_time::time_duration timeout) const {
        if(Wait(timeout) == PENDING) {
          BOOST_THROW_EXCEPTION(TimeoutException());
        }
        return Get();
      }

      //! Returns the state of this Async.
      State GetState() const {
        assert(this->m_state != PENDING);
        return this->m_state;
      }

      //! Waits for the operation to complete.
      /*!
        \param timeout The amount of time to wait.
        \return The current State of the Async.
      */
      State Wait(boost::posix_time::time_duration timeout) const {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        if(this->m_state == PENDING) {
          if(timeout == boost::posix_time::pos_infin) {
            this->m_isFinishedCondition.wait(lock);
            assert(this->m_state != PENDING);
          } else {
            this->m_isFinishedCondition.timed_wait(lock, timeout);
          }
        }
        return this->m_state;
      }

      //! Sets the FinishedSlot, called when the operation finished.
      /*!
        \param finishedSlot The slot to call upon completion.
        \param state The current State of this Async.
      */
      void SetFinishedSlot(const FinishedSlot& finishedSlot, Out<State> state) {
        boost::lock_guard<boost::mutex> lock(this->m_mutex);
        *state = this->m_state;
        if(this->m_state != PENDING) {
          return;
        }
        m_finishedSlot.reset(new FinishedSlot(finishedSlot));
      }

    private:
      mutable boost::mutex m_mutex;
      mutable boost::condition_variable m_isFinishedCondition;
      State m_state;
      typedef typename boost::aligned_storage<sizeof(T),
        boost::alignment_of<T>::value>::type ResultStorage;
      ResultStorage m_storage;
      T* m_result;
      boost::exception_ptr m_exception;
      boost::shared_ptr<FinishedSlot> m_finishedSlot;

      void SetState(State state, boost::unique_lock<boost::mutex>& lock) {
        assert(this->m_state == PENDING);
        assert(state != PENDING);
        this->m_state = state;
        this->m_isFinishedCondition.notify_all();
        if(m_finishedSlot == NULL) {
          return;
        }
        boost::shared_ptr<FinishedSlot> finishedSlot = m_finishedSlot;
        lock.unlock();
        (*finishedSlot)();
      }
  };

  /*! \class Async
   *  \brief Specialization for void operations.
   */
  template<>
  class Async<void> : private boost::noncopyable {
    public:

      //! Called when the Async is no longer PENDING.
      typedef boost::function<void ()> FinishedSlot;

      /*! \enum State
       *  \brief Enumerates the states of an operation.
       */
      enum State {

        //! The operation is currently pending.
        PENDING = 1,

        //! The operation succeeded.
        COMPLETE = 2,

        //! The operation resulted in an exception.
        EXCEPTION = 4,
      };

      //! Constructs an Async.
      Async();

      //! Constructs an Async whose result has already been computed.
      /*!
        \param result Unused.
      */
      Async(bool result);

      ~Async();

      //! Sets the result of this Async.
      void SetResult();

      //! Returns the exception.
      const boost::exception_ptr& GetException() const;

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      template<typename E>
      void SetException(const E& e) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == PENDING);
        m_exception = boost::copy_exception(e);
        SetState(EXCEPTION, lock);
      }

      //! Sets an exception to be returned.
      /*!
        \param e The exception to set.
      */
      void SetException(const boost::exception_ptr& e);

      //! Copies the exception held by another async.
      /*!
        \param async The async to copy the exception from.
      */
      template<typename U>
      void CopyException(const Async<U>& async) {
        boost::unique_lock<boost::mutex> lock(this->m_mutex);
        assert(this->m_state == PENDING);
        m_exception = async.GetException();
        SetState(EXCEPTION, lock);
      }

      //! Resets the result of the Async, permitting its reuse.
      void ResetResult();

      //! Resets this Async including all slots, permitting its reuse.
      void Reset();

      //! Returns the result of the operation.
      void Get() const;

      //! Returns the result of the operation.
      /*!
        \param timeout The amount of time to wait before timing out.
        \return The result of the operation.
      */
      void Get(boost::posix_time::time_duration timeout) const;

      //! Returns the state of this Async.
      State GetState() const;

      //! Waits for the operation to finish.
      /*!
        \return The State of the operation.
      */
      template<typename Lock>
      State Wait(Lock& lock) const {
        boost::unique_lock<boost::mutex> waitLock(this->m_mutex);
        if(this->m_state != PENDING) {
          return this->m_state;
        }
        lock.unlock();
        m_isFinishedCondition.wait(waitLock);
        lock.lock();
        assert(this->m_state != PENDING);
        return this->m_state;
      }

      //! Waits for the operation to complete.
      /*!
        \param timeout The amount of time to wait.
        \return The current State of the Async.
      */
      State Wait(boost::posix_time::time_duration timeout) const;

      //! Sets the FinishedSlot, called when the operation finished.
      /*!
        \param finishedSlot The slot to call upon completion.
        \param state The current State of this Async.
      */
      void SetFinishedSlot(const FinishedSlot& finishedSlot, Out<State> state);

    private:
      template<typename T>
      friend class Async;
      mutable boost::mutex m_mutex;
      mutable boost::condition_variable m_isFinishedCondition;
      State m_state;
      boost::exception_ptr m_exception;
      boost::shared_ptr<FinishedSlot> m_finishedSlot;

      void SetState(State state, boost::unique_lock<boost::mutex>& lock);
  };
}
}

#endif // AVALON_ASYNC_HPP
